﻿using System;
using System.Collections.Generic;
using System.Text;

namespace IMMeDotNet {
	
	/// <summary>
	/// Represents a string of <see cref="IMMeChar"/>s.
	/// </summary>
	public class IMMeString : IComparable<IMMeString> {

		private IMMeChar[] characters;

		/// <summary>
		/// Creates an instance of a <see cref="IMMeString"/> from an array of <see cref="IMMeChar"/>s.
		/// </summary>
		/// <param name="characters">The characters to create the string from.</param>
		public IMMeString(IMMeChar[] characters) {
			this.characters = characters;
		}

		/// <summary>
		/// Creates an instance of a <see cref="IMMeString"/> from an array of bytes.
		/// </summary>
		/// <param name="data">The data to create the <see cref="IMMeString"/> from.</param>
		public IMMeString(byte[] data) {
			this.characters = Array.ConvertAll(data, b => (IMMeChar)b);
		}

		/// <summary>
		/// Creates an instance of a <see cref="IMMeString"/> from a regular string.
		/// </summary>
		/// <param name="s">The string to create the instance from.</param>
		public IMMeString(string s) {
			var conversionChars = new List<KeyValuePair<string, IMMeChar>>();
			foreach (IMMeChar c in Enum.GetValues(typeof(IMMeChar))) {
				conversionChars.Add(new KeyValuePair<string, IMMeChar>(StringRepresentationAttribute.GetString(c), c));
			}
			conversionChars.Sort((a, b) => b.Key.Length.CompareTo(a.Key.Length));

			var convertedString = new List<IMMeChar>(s.Length);
			while (s.Length > 0) {
				var foundCharacter = false;
				foreach (var item in conversionChars) {
					if (s.StartsWith(item.Key)) {
						foundCharacter = true;
						convertedString.Add(item.Value);
						s = s.Substring(item.Key.Length);
						break;
					}
				}
				if (!foundCharacter) {
					convertedString.Add(IMMeChar.QuestionMark);
					s = s.Substring(1);
				}
			}

			this.characters = convertedString.ToArray();
		}

		/// <summary>
		/// Converts the <see cref="IMMeString"/> into a string.
		/// </summary>
		/// <returns>A string representation of the <see cref="IMMeString"/>. Note that some characters are not directly convertable (such as emoticons) and will be returned as approximations.</returns>
		public override string ToString() {
			var result = new StringBuilder(this.characters.Length);
			foreach (var character in this.characters) {
				// Check for characters that match ASCII.
				if ((character >= IMMeChar.Space && character <= IMMeChar.DollarSign) ||
					(character >= IMMeChar.Ampersand && character <= IMMeChar.RightParenthesis) ||
					(character >= IMMeChar.PlusSign && character <= IMMeChar.FullStop) ||
					(character >= IMMeChar.DigitZero && character <= IMMeChar.Semicolon) ||
					(character == IMMeChar.EqualsSign) ||
					(character >= IMMeChar.QuestionMark && character <= IMMeChar.LatinCapitalLetterZ) ||
					(character == IMMeChar.ReverseSolidus) ||
					(character == IMMeChar.LowLine) ||
					(character >= IMMeChar.LatinSmallLetterA && character <= IMMeChar.LatinSmallLetterZ)) {
					result.Append((char)character);
				} else {
					// Use the attached attribute to perform the conversion.
					var characterString = StringRepresentationAttribute.GetString(character);
					if (characterString != null) {
						result.Append(characterString);
					} else {
						result.Append('?');
					}
				}
			}
			return result.ToString();
		}

		/// <summary>
		/// Converts the <see cref="IMMeString"/> into an array of <see cref="IMMeChar"/>s.
		/// </summary>
		/// <returns>An array of <see cref="IMMeChar"/>s that represents the string.</returns>
		public IMMeChar[] ToCharArray() {
			var result = new IMMeChar[this.characters.Length];
			Array.Copy(this.characters, result, result.Length);
			return result;
		}

		/// <returns>An array of bytes that represents the string.</returns>
		public byte[] ToByteArray() {
			return Array.ConvertAll(this.characters, c => (byte)c);
		}


		#region IComparable<IMMeString> Members

		/// <summary>
		/// Compares two strings.
		/// </summary>
		/// <param name="other">The string to compare to.</param>
		public int CompareTo(IMMeString other) {
			int minLength = Math.Min(this.characters.Length, other.characters.Length);
			for (int i = 0; i < minLength; ++i) {
				var result = this.characters[i].CompareTo(other.characters[i]);
				if (result != 0) return result;
			}
			return this.characters.Length.CompareTo(other.characters.Length);
		}

		#endregion

		public override bool Equals(object obj) {
			return this == (obj as IMMeString);
		}

		public override int GetHashCode() {
			return Crc32.Calculate(this.ToByteArray());
		}

		/// <summary>
		/// Compares two <see cref="IMMeString"/>s for equality.
		/// </summary>
		/// <param name="a">One of the strings to compare.</param>
		/// <param name="b">The other string to compare.</param>
		/// <returns>True if the strings are the same; false otherwise.</returns>
		public static bool operator ==(IMMeString a, IMMeString b) {
			if ((object)a == null || (object)b == null) {
				return (object)a == (object)b;
			} else if (a.characters.Length != b.characters.Length) {
				return false;
			} else {
				for (int i = 0; i < a.characters.Length; ++i) {
					if (a.characters[i] != b.characters[i]) {
						return false;
					}
				}
				return true;
			}
		}

		/// <summary>
		/// Compares two <see cref="IMMeString"/>s for inequality.
		/// </summary>
		/// <param name="a">One of the strings to compare.</param>
		/// <param name="b">The other string to compare.</param>
		/// <returns>True if the strings are different; false otherwise.</returns>
		public static bool operator !=(IMMeString a, IMMeString b) {
			return !(a == b);
		}

	}
}
